#include <dlfcn.h>
#include <stdarg.h>

#include "log.h"
#include "hook.h"
#include "fiber.h"
#include "iomanager.h"
#include "scheduler.h"
#include "fd_manage.h"
#include "config.h"

static qtch::Logger::ptr logger = QTCH_LOG_NAME("system");

namespace qtch{

ConfigVar<int>::ptr g_tcp_connect_timeout = qtch::Config::LookUp("tcp.connect.timeout",5000,"tcp connect timeout");

static thread_local bool t_hook_enable = false;

#define HOOK_FUN(name) \
    name(sleep) \
    name(usleep) \
    name(nanosleep) \
    name(socket) \
    name(connect) \
    name(accept) \
    name(read) \
    name(readv) \
    name(recv) \
    name(recvfrom) \
    name(recvmsg) \
    name(write) \
    name(writev) \
    name(send) \
    name(sendto) \
    name(sendmsg) \
    name(close) \
    name(fcntl) \
    name(ioctl) \
    name(getsockopt) \
    name(setsockopt) \
    name(shutdown)

bool is_hook_enable(){
    return t_hook_enable;
}

void set_hook_enable(bool flag){
    t_hook_enable = flag;
}

void hook_init(){
    static bool is_inited = false;
    if(is_inited){
        return;
    }
    #define XX(name) name ## _f = (name ## _fun)dlsym(RTLD_NEXT, #name);
        HOOK_FUN(XX)
    #undef XX
}
static uint64_t s_connect_timeout = 0;
struct _HookIniter{
    _HookIniter(){
        s_connect_timeout = g_tcp_connect_timeout->getValue();
        g_tcp_connect_timeout->addListener([](const int& old_value, const int& new_value){
            s_connect_timeout = new_value;
        });
        hook_init();
    }
};
static _HookIniter s_hook_initer;

}

struct timer_info {
    int cancelled = 0;
};

template<typename OriginFun, typename... Args>
static ssize_t do_io(int fd, OriginFun fun,const char* hook_fun_name,
        uint32_t event, int timeout_so, Args&&... args){

    if(!qtch::t_hook_enable){
        return fun(fd,std::forward<Args>(args)...);
    }
    qtch::FdCtx::ptr ctx = qtch::FdMgr::GetInstance()->get(fd);
    if(!ctx){
        return fun(fd,std::forward<Args>(args)...);
    }

    if(ctx->isClosed()){
        errno = EBADF;
        return -1;
    }
    
    if(!ctx->isSocket() || ctx->getUserNonblock()){
        return fun(fd,std::forward<Args>(args)...);
    }

    uint64_t to = ctx->getTimeout(timeout_so);
    std::shared_ptr<timer_info> tinfo(new timer_info);
retry:
    ssize_t n = fun(fd,std::forward<Args>(args)...);
    while(n == -1 && errno == EINTR){
        n = fun(fd,std::forward<Args>(args)...);
    }
    if(n == -1 && errno == EAGAIN){
        qtch::IOManager* iom = qtch::IOManager::getThis();
        qtch::Timer::ptr timer;
        std::weak_ptr<timer_info> winfo(tinfo);

        if(to != (uint64_t)-1){
            timer = iom->addConditionTimer(to,[winfo,fd,iom,event](){
                auto it = winfo.lock();
                if(!it || it->cancelled){
                    return;
                }
                it->cancelled = ETIMEDOUT;
                iom->cancelEvent(fd,(qtch::IOManager::Event)event);
            },winfo);
        }

        int rt  = iom->addEvent(fd,(qtch::IOManager::Event)event);
        if(rt){
            QTCH_LOG_ERROR(logger) << hook_fun_name << " addEvent("
                << fd << "," << event << ")";
            if(timer){
                timer->cancel();
            }
            return -1;
        }
        qtch::Fiber::yieldToHold();
        if(timer){
            timer->cancel();
        }
        if(tinfo->cancelled){
            errno = tinfo->cancelled;
            return -1;
        }
        goto retry;

    }
    return n;
}




extern "C" {


#define XX(name) name ## _fun name ## _f = nullptr;
    HOOK_FUN(XX);
#undef XX

unsigned int sleep(unsigned int seconds){
    if(!qtch::t_hook_enable){
        return sleep_f(seconds);
    }
    qtch::Fiber::ptr fiber = qtch::Fiber::GetThis();
    qtch::IOManager* iom = qtch::IOManager::getThis();
    iom->addTimer(seconds * 1000,std::bind((void (qtch::Scheduler::*)
            (qtch::Fiber::ptr fc,int thread))&qtch::IOManager::schedule,iom,fiber,-1));
    qtch::Fiber::yieldToHold();
    return 0;
}

int usleep(useconds_t usec){
    if(!qtch::t_hook_enable){
        return usleep_f(usec);
    }
    qtch::Fiber::ptr fiber = qtch::Fiber::GetThis();
    qtch::IOManager* iom = qtch::IOManager::getThis();
    iom->addTimer(usec / 1000,std::bind((void (qtch::Scheduler::*)
            (qtch::Fiber::ptr fc,int thread))&qtch::IOManager::schedule,iom,fiber,-1));
    qtch::Fiber::yieldToHold();
    return 0;
}

int nanosleep(const struct timespec *req, struct timespec *rem){
    if(!qtch::t_hook_enable){
        return nanosleep_f(req,rem);
    }
    uint64_t time_out = req->tv_sec * 1000 + req->tv_nsec / 1000 / 1000;
    qtch::Fiber::ptr fiber = qtch::Fiber::GetThis();
    qtch::IOManager* iom = qtch::IOManager::getThis();
    iom->addTimer(time_out,std::bind((void (qtch::Scheduler::*)
            (qtch::Fiber::ptr fc,int thread))&qtch::IOManager::schedule,iom,fiber,-1));
    qtch::Fiber::yieldToHold();
    return 0;
}



int socket(int domain, int type, int protocol){
    if(!qtch::t_hook_enable){
        return socket_f(domain,type, protocol);
    }
    int fd = socket_f(domain,type, protocol);
    QTCH_LOG_DEBUG(logger) << "socket fd:" <<fd << " socket(" << domain << ", " << type << ", " << protocol << ")";
    if(fd != -1){
        qtch::FdMgr::GetInstance()->get(fd,true);
    }

    return fd;
}

int connect_with_timeout(int sockfd, const struct sockaddr *addr,socklen_t addrlen, uint64_t timeout_ms){
    if(!qtch::t_hook_enable){
        return connect_f(sockfd, addr, addrlen);
    }
    qtch::FdCtx::ptr ctx = qtch::FdMgr::GetInstance()->get(sockfd);
    if(!ctx || ctx->isClosed()){
        errno = EBADF;
        return -1;
    }
    if(!ctx->isSocket() || ctx->getUserNonblock()){
        return connect_f(sockfd, addr, addrlen);
    }

    int n = connect_f(sockfd, addr, addrlen);
    if(n==0){
        return 0;
    }else if(n != -1 || errno != EINPROGRESS){
        return n;
    }
    std::shared_ptr<timer_info> tinfo(new timer_info);
    qtch::IOManager* iom = qtch::IOManager::getThis();
    qtch::Timer::ptr timer;
    std::weak_ptr<timer_info> winfo(tinfo);

    timer = iom->addConditionTimer(timeout_ms,[winfo,sockfd,iom](){
        auto it = winfo.lock();
        if(!it || it->cancelled){
            return;
        }
        it->cancelled = ETIMEDOUT;
        iom->cancelEvent(sockfd,qtch::IOManager::Event::WRITE);
    },winfo);

    int rt = iom->addEvent(sockfd, qtch::IOManager::Event::WRITE);
    if(rt == 0) {
        qtch::Fiber::yieldToHold();
        if(timer){
            timer->cancel();
        }
        if(tinfo->cancelled){
            errno = tinfo->cancelled;
            return -1;
        }
    }
    else{
        if(timer){
            timer->cancel();
        }
        QTCH_LOG_ERROR(logger) << "connect addEvent(" <<sockfd << ", " << "WRITE) error"; 
    }

    int error = 0;
    socklen_t len = sizeof(int);
    if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len)== -1){
        return -1;
    }
    if(!error) {
        return 0;
    }
    else{
        errno = error;
        return -1;
    }

}

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen){
    return connect_with_timeout(sockfd, addr, addrlen,qtch::s_connect_timeout);
}

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){
    int fd = do_io(sockfd, accept_f, "accept", qtch::IOManager::READ, SO_RCVTIMEO, addr, addrlen);
    if(fd >= 0){
        qtch::FdMgr::GetInstance()->get(fd,true);
    }
    return fd;
}

ssize_t read(int fd, void *buf, size_t count){
    return do_io(fd,read_f,"read",qtch::IOManager::Event::READ,SO_RCVTIMEO,buf,count);
}

ssize_t readv(int fd, const struct iovec *iov, int iovcnt){
    return do_io(fd,readv_f,"readv",qtch::IOManager::Event::READ,SO_RCVTIMEO,iov,iovcnt);
}

ssize_t recv(int sockfd, void *buf, size_t len, int flags){
    return do_io(sockfd,recv_f,"recv",qtch::IOManager::Event::READ,SO_RCVTIMEO,buf,len,flags);
}

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen){
    return do_io(sockfd,recvfrom_f,"recvfrom",qtch::IOManager::Event::READ,SO_RCVTIMEO,buf,len,flags,src_addr,addrlen);
}

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags){
    return do_io(sockfd,recvmsg_f,"recvmsg",qtch::IOManager::Event::READ,SO_RCVTIMEO,msg,flags);
}

ssize_t write(int fd, const void *buf, size_t count){
    return do_io(fd,write_f,"write",qtch::IOManager::Event::WRITE,SO_SNDTIMEO,buf,count);
}

ssize_t writev(int fd, const struct iovec *iov, int iovcnt){
    return do_io(fd,writev_f,"writev",qtch::IOManager::Event::WRITE,SO_SNDTIMEO,iov,iovcnt);
}

ssize_t send(int s, const void *msg, size_t len, int flags){
    return do_io(s,send_f,"send",qtch::IOManager::Event::WRITE,SO_SNDTIMEO,msg,len,flags);
}

ssize_t sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen){
    return do_io(s,sendto_f,"sendto",qtch::IOManager::Event::WRITE,SO_SNDTIMEO,msg,len,flags,to,tolen);
}

ssize_t sendmsg(int s, const struct msghdr *msg, int flags){
    return do_io(s,sendmsg_f,"sendmsg",qtch::IOManager::Event::WRITE,SO_SNDTIMEO,msg,flags);
}

int close(int fd){
    QTCH_LOG_DEBUG(logger) << "close fd:" <<fd;
    if(!qtch::is_hook_enable()){
        return close_f(fd);
    }
    qtch::FdCtx::ptr ctx = qtch::FdMgr::GetInstance()->get(fd);
    if(ctx){
        auto iom = qtch::IOManager::getThis();
        if(iom){
            iom->cancelEventAll(fd);
        }
        qtch::FdMgr::GetInstance()->del(fd);
    }
    return close_f(fd);
}

int fcntl(int fd, int cmd, ... /* arg */ ){
    va_list va;
    va_start(va,cmd);
    switch(cmd){
        case F_SETFL:
            {
                int args = va_arg(va,int);
                va_end(va);
                qtch::FdCtx::ptr ctx = qtch::FdMgr::GetInstance()->get(fd);
                if(!ctx || ctx->isClosed() || !ctx->isSocket()){
                    return fcntl_f(fd,cmd,args);
                }
                ctx->setUserNonblock(args & O_NONBLOCK);
                if(ctx->getSysNonblock()){
                    args |= O_NONBLOCK;
                }
                else{
                    args &= ~O_NONBLOCK;
                }
                QTCH_LOG_DEBUG(logger) << "fd=" << fd << " O_NONBLOCK:" <<(args&O_NONBLOCK);
                return fcntl_f(fd,cmd,args);
            }
            break;
        case F_GETFL:
            {
                va_end(va);
                int arg = fcntl_f(fd, cmd);
                qtch::FdCtx::ptr ctx = qtch::FdMgr::GetInstance()->get(fd);
                QTCH_LOG_DEBUG(logger) << "fd=" << fd << " O_NONBLOCK:" <<(arg&O_NONBLOCK);
                if(!ctx || ctx->isClosed() || !ctx->isSocket()){
                    return arg;
                }
                if(ctx->getUserNonblock()){
                    return arg | O_NONBLOCK;
                }
                else{
                    return arg & (~O_NONBLOCK);
                }

            }
            break;
        // int
        case F_DUPFD:
#ifdef F_DUPFD_CLOEXEC
        case F_DUPFD_CLOEXEC:
#endif
        case F_SETFD:
        case F_SETOWN:
        case F_SETSIG:
        case F_SETLEASE:
        case F_NOTIFY:
#ifdef F_SETPIPE_SZ
        case F_SETPIPE_SZ:
#endif  
#ifdef F_ADD_SEALS
        case F_ADD_SEALS:
#endif
            {
                int args = va_arg(va,int);
                va_end(va);
                return fcntl_f(fd,cmd,args);
            }
            break;
        // void
        case F_GETFD:
        case F_GETOWN:
        case F_GETSIG:
        case F_GETLEASE:
#ifdef F_GETPIPE_SZ
        case F_GETPIPE_SZ:
#endif
#ifdef F_GET_SEALS
        case F_GET_SEALS:
#endif
            {
                va_end(va);
                return fcntl_f(fd, cmd);
            }
            break;

        // flock *
        case F_SETLK:
        case F_SETLKW:
        case F_GETLK:
#ifdef F_OFD_SETLK
        case F_OFD_SETLK:
#endif
#ifdef F_OFD_SETLKW
        case F_OFD_SETLKW:
#endif
#ifdef F_OFD_GETLK
        case F_OFD_GETLK:
#endif
            {
                struct flock * arg = va_arg(va,struct flock *);
                va_end(va);
                return fcntl_f(fd, cmd, arg);
            }
            break;
        // f_owner_ex *
#ifdef F_GETOWN_EX
        case F_GETOWN_EX:
#endif
        case F_SETOWN_EX:
            {
                struct f_owner_ex * arg = va_arg(va,struct f_owner_ex *);
                va_end(va);
                return fcntl_f(fd, cmd, arg);
            }
            break;
        // uint64_t *
#ifdef F_GET_RW_HINT
        case F_GET_RW_HINT:
        case F_SET_RW_HINT:
        case F_GET_FILE_RW_HINT:
        case F_SET_FILE_RW_HINT:
            {
                uint64_t * arg = va_arg(va,uint64_t *);
                va_end(va);
                return fcntl_f(fd, cmd, arg);
            }
            break;
#endif
        default:
            va_end(va);
            return fcntl_f(fd,cmd);
    }
}

int ioctl(int d, unsigned long int request, ...){
    va_list va;
    va_start(va, request);
    void* arg = va_arg(va, void*);
    va_end(va);
    if(!qtch::is_hook_enable()){
        return ioctl_f(d,request,arg);
    }
    if(request == FIONBIO){
        bool user_nonblock = !! *(int *)arg;
        qtch::FdCtx::ptr ctx = qtch::FdMgr::GetInstance()->get(d);
        if(!ctx || ctx->isClosed() || !ctx->isSocket()){
            return ioctl_f(d,request,arg);
        }
        ctx->setUserNonblock(user_nonblock);
    }
    return ioctl_f(d,request,arg);
}

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen){
    return getsockopt_f(sockfd,level,optname,optval,optlen);
}

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen){
    if(!qtch::is_hook_enable()){
        return setsockopt_f(sockfd,level,optname,optval,optlen);
    }
    if(level == SOL_SOCKET){
        if(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO){
            qtch::FdCtx::ptr ctx = qtch::FdMgr::GetInstance()->get(sockfd);
            if(ctx){
                const timeval * v = (const timeval *)optval;
                ctx->setTimeout(optname, v->tv_sec * 1000 + v->tv_usec / 1000);
            }
        }
    }
    return setsockopt_f(sockfd,level,optname,optval,optlen);
}

int shutdown(int sockfd, int how){
    if(!qtch::is_hook_enable()){
        return shutdown_f(sockfd,how);
    }
    QTCH_LOG_DEBUG(logger) << "shutdown fd:" <<sockfd << " how="<<how;
    qtch::IOManager* iom = qtch::IOManager::getThis();
    if(iom){
        switch(how){
            case SHUT_RD:
                iom->delEvent(sockfd,qtch::IOManager::Event::READ);
                break;
            case SHUT_WR:
                iom->delEvent(sockfd,qtch::IOManager::Event::WRITE);
                break;
            case SHUT_RDWR:
                iom->delEventAll(sockfd);
                break;

        }
    }
    return shutdown_f(sockfd,how);

}



}